//  IDVI 1.0 source copyright 1996 Garth A. Dickie
//
//  This source is free for non-commercial use.  No warranty, etc.
//  Please acknowledge reuse by including the line:
//
//  "Based in part on IDVI 1.0 source copyright 1996 Garth A. Dickie"
//
//  in your documentation and source code.  For commercial use or
//  distribution, please contact the author.  Please also send
//  questions, comments, bug reports, or fixes.
//
//  Best Regards,
//  Garth A. Dickie
//  dickie@elastic.avid.com

package ibook.v10.idvi.display;

import java.awt.Graphics;

class ContainerView extends View {
    final private static int    kHasComponentsInitialLength = 1;
    final private static int    kGetsMouseEventsInitialLength = 1;

    private ScaledColorScheme   colorScheme;
    private int                 scale;
    private ViewPanel           panel;

    private View                parent;
    private int                 indexInParent;

    private int                 childCount = 0;
    private View[ ]             child;
    private boolean[ ]          unset;              //  unset flag from bounding box
    private int[ ]              top;                //  un-offset top of bounding box
    private int[ ]              left;               //  left of bounding box
    private int[ ]              bottom;             //  un-offset, but expanded, bottom of bounding box
    private int[ ]              right;              //  right of bounding box
    private int[ ]              offset;             //  offset due to expansion of other children of this Container

    private int                 hasComponentsCount = 0;
    private int[ ]              hasComponents;      //  indices of children which contain awt Components
    private int                 componentXOffset;
    private int                 componentYOffset;

    private int                 getsMouseEventsCount = 0;
    private int[ ]              getsMouseEvents;    //  indices of children which need mouse events

    private DVIRectangle        childBounds;        // scratch space during loading
    private DVIRectangle        newBounds;          // scratch space during loading
    private DVIRectangle        totalBounds;
    private int                 totalFlags;
    private int                 totalExpansion;

    ContainerView( ScaledColorScheme colorScheme, int scale, ViewPanel panel ) {
        this.colorScheme = colorScheme;
        this.scale       = scale;
        this.panel       = panel;

        childBounds = new DVIRectangle( );
        newBounds   = new DVIRectangle( );
        totalBounds = new DVIRectangle( );
        totalFlags = 0;
        totalExpansion = 0;
    }

    //  Methods used by ContainerBlock:
    //
    //  addChildren adds Views for any new children of the Block.  The
    //  parameters are the entire child, x, and y, arrays from the Block,
    //  with a count of how many entries are currently used.  We compare
    //  with our count of existing child Views to see if we need to create
    //  any new Views.

    void addChildren( int blockChildCount, Block[ ] blockChild, int[ ] x, int[ ] y ) {
        if( childCount != blockChildCount ) {
            if( child == null ) {
                child  = new View   [ blockChildCount ];
                unset  = new boolean[ blockChildCount ];
                top    = new int    [ blockChildCount ];
                left   = new int    [ blockChildCount ];
                bottom = new int    [ blockChildCount ];
                right  = new int    [ blockChildCount ];
                offset = new int    [ blockChildCount ];
            } else if( blockChildCount >= child.length )
                setChildLength( Math.max( 2 * child.length, blockChildCount ));
            
            newBounds.set( );       // start with an unset bounds rectangle
            int newFlags = 0;       // and no flags
            int newExpansion = 0;   // and the same expansion as before

            for( int i = childCount; i < blockChildCount; ++ i ) {
                child [ i ] = blockChild[ i ].getView( colorScheme, scale, panel );
                child[ i ].setParent( this, i );

                // This must be *here*, for getBottomOfPreviousChild( ) to use,
                // when it is called from ToggleView.getBounds( ).
                offset[ i ] = totalExpansion + newExpansion;

                int childFlags = child[ i ].getFlags( );
                int expansion = child[ i ].getBounds( childBounds, scale, x[ i ], y[ i ], blockChild[ i ] );

                unset [ i ] = childBounds.unset;
                top   [ i ] = childBounds.top;
                left  [ i ] = childBounds.left;
                bottom[ i ] = childBounds.bottom + expansion;
                right [ i ] = childBounds.right;

                newExpansion += expansion;
                newBounds.union( childBounds );
                newFlags |= childFlags;

                if(( childFlags & kFlagHasComponents ) != 0 ) {
                    addHasComponents( i );
                    if(( totalFlags & kFlagHasComponents ) != 0 )
                        child[ i ].showComponents( componentXOffset, componentYOffset + offset[ i ] );
                }
                
                if(( childFlags & kFlagGetsMouseEvents ) != 0 )
                    addGetsMouseEvents( i );
            }

            childCount = blockChildCount;
            totalBounds.union( newBounds );
            totalExpansion += newExpansion;
            newFlags &= ~ totalFlags;       // keep track of which flags are really new
            totalFlags |= newFlags;

            if( parent != null ) {
                if( newExpansion != 0 )
                    parent.childChangedExpansion( indexInParent, newExpansion, totalBounds.bottom );

                //  childAddedToBounds( ) updates bounds rectangles, but causes no drawing.
                if( ! newBounds.unset )
                    parent.childAddedToBounds( totalBounds, totalExpansion );
                
                //  childAddedToFlags( ) may force a call to showComponents.
                if( newFlags != 0 )
                    parent.childAddedToFlags( newFlags );
                
                if( ! newBounds.unset )
                    parent.repaint( newBounds, indexInParent, kRepaintDelayProgressive );
            }
        }
    }

    void doneAddingChildren( ) {
        if( child != null && childCount != child.length )
            setChildLength( childCount );
        
        if( hasComponents != null && hasComponentsCount != hasComponents.length )
            setHasComponentsLength( hasComponentsCount );
        
        if( getsMouseEvents != null && getsMouseEventsCount != getsMouseEvents.length )
            setGetsMouseEventsLength( getsMouseEventsCount );

        childBounds = null;
        newBounds = null;
    }

    //  Overridden View methods:

    void setParent( View parent, int indexInParent ) {
        this.parent = parent;
        this.indexInParent = indexInParent;
    }

    int getFlags( ) {
        return totalFlags;
    }

    int getBounds( DVIRectangle bounds, int scale, int x, int y, Block block ) {
        bounds.set( totalBounds );

        return totalExpansion;
    }

    void showComponents( int componentXOffset, int componentYOffset ) {
        this.componentXOffset = componentXOffset;
        this.componentYOffset = componentYOffset;

        for( int i = 0; i < hasComponentsCount; ++ i )
            child[ hasComponents[ i ]].showComponents( componentXOffset, componentYOffset + offset[ hasComponents[ i ]] );
    }

    void hideComponents( ) {
        for( int i = 0; i < hasComponentsCount; ++ i )
            child[ hasComponents[ i ]].hideComponents( );
    }

    void paint( Graphics g, int color, DVIRectangle bounds, DVIRectangle clip, int yOffset ) {
        for( int i = 0; i < childCount; ++ i )
            if( ! unset[ i ] ) {
                int wholeOffset = offset[ i ] + yOffset;

                bounds.unset  = false;
                bounds.top    = top[ i ] + wholeOffset;
                bounds.left   = left[ i ];
                bounds.bottom = bottom[ i ] + wholeOffset;
                bounds.right  = right[ i ];

                if( bounds.intersects( clip ))
                    child[ i ].paint( g, color, bounds, clip, wholeOffset );
            }
    }

    //  Mouse handling routines:
    //
    //  

    View        currentTarget = null;
    int         currentTargetIndex;
    boolean     dragging = false;
    boolean     draggingInside = false;

    void mouseEnter( int x, int y ) {
        if( dragging ) {
            if( currentTarget != null && locateCheck( currentTargetIndex, x, y )) {
                currentTarget.mouseEnter( x, y - offset[ currentTargetIndex ] );
                draggingInside = true;
            }
        } else {
            currentTargetIndex = locate( x, y );
            currentTarget = currentTargetIndex == -1 ? null : child[ currentTargetIndex ];

            if( currentTarget != null )
                currentTarget.mouseEnter( x, y - offset[ currentTargetIndex ] );
        }
    }

    void mouseMove( int x, int y ) {
        if( currentTarget != null && locateCheck( currentTargetIndex, x, y ))
            currentTarget.mouseMove( x, y - offset[ currentTargetIndex ] );
        else {
            if( currentTarget != null )
                currentTarget.mouseExit( );
            
            currentTargetIndex = locate( x, y );
            currentTarget = currentTargetIndex == -1 ? null : child[ currentTargetIndex ];

            if( currentTarget != null )
                currentTarget.mouseEnter( x, y - offset[ currentTargetIndex ] );
        }
    }

    boolean mouseDown( int clickCount ) {
        boolean result = false;

        if( currentTarget != null )
            result = currentTarget.mouseDown( clickCount );
        
        dragging = true;
        draggingInside = true;
        
        return result;
    }

    void mouseDrag( int x, int y ) {
        if( currentTarget != null ) {
            boolean inside = locateCheck( currentTargetIndex, x, y );

            if( inside )
                if( draggingInside )
                    currentTarget.mouseDrag( x, y - offset[ currentTargetIndex ] );
                else
                    currentTarget.mouseEnter( x, y - offset[ currentTargetIndex ] );
            else
                if( draggingInside )
                    currentTarget.mouseExit( );
            
            draggingInside = inside;
        }
    }

    boolean mouseUp( int modifiers ) {
        boolean result = false;

        if( currentTarget != null )
            result = currentTarget.mouseUp( modifiers );
        
        dragging = false;

        return result;
    }

    void mouseExit( ) {
        if( currentTarget != null ) {
            if( dragging ) {
                if( draggingInside ) {
                    currentTarget.mouseExit( );
                    draggingInside = false;
                }
            } else {
                currentTarget.mouseExit( );
                currentTarget = null;
            }
        }
    }

    private int locate( int x, int y ) {
        for( int i = 0; i < getsMouseEventsCount; ++ i )
            if( locateCheck( getsMouseEvents[ i ], x, y ))
                return getsMouseEvents[ i ];
        
        return -1;
    }

    private boolean locateCheck( int index, int x, int y ) {
        boolean result = ! unset[ index ] &&
            x >= left[ index ] && x <= right[ index ] &&
            y >= top[ index ] + offset[ index ] && y <= bottom[ index ] + offset[ index ];
        
        return result;
    }




    //  This method is only called during loading, and we are permitted
    //  to assume that the child in question is the last child in our list.

    void childAddedToBounds( DVIRectangle newBounds, int expansion ) {
        int i = childCount - 1;

        unset [ i ] = newBounds.unset;
        top   [ i ] = newBounds.top;
        left  [ i ] = newBounds.left;
        bottom[ i ] = newBounds.bottom + expansion;
        right [ i ] = newBounds.right;

        totalBounds.union( newBounds );

        if( parent != null && ! totalBounds.unset )
            parent.childAddedToBounds( totalBounds, totalExpansion );
    }

    void childAddedToFlags( int newFlags ) {
        int i = childCount - 1;

        if(( newFlags & kFlagHasComponents ) != 0 ) {
            addHasComponents( i );
            if(( totalFlags & kFlagHasComponents ) != 0 )
                child[ i ].showComponents( componentXOffset, componentYOffset + offset[ i ] );
        }

        if(( newFlags & kFlagGetsMouseEvents ) != 0 )
            addGetsMouseEvents( i );

        newFlags &= ~ totalFlags;
        totalFlags |= newFlags;

        if( parent != null && newFlags != 0 )
            parent.childAddedToFlags( newFlags );
    }

    void childChangedExpansion( int childIndex, int expansionDelta, int yPosition ) {
        totalExpansion += expansionDelta;
        bottom[ childIndex ] += expansionDelta;
        for( int i = childIndex + 1; i < childCount; ++ i )
            offset[ i ] += expansionDelta;
        
        if( parent != null )
            parent.childChangedExpansion( indexInParent, expansionDelta, yPosition + offset[ childIndex ] );
        
        for( int i = 0; i < hasComponentsCount; ++ i ) {
            int afterIndex = hasComponents[ i ];
            if( afterIndex > childIndex )
                child[ afterIndex ].showComponents( componentXOffset, componentYOffset + offset[ afterIndex ] );
        }
    }

    void childBounds( DVIRectangle childBounds, int childIndex ) {
        childBounds.unset = unset[ childIndex ];

        if( ! childBounds.unset ) {
            childBounds.top    = top   [ childIndex ];
            childBounds.left   = left  [ childIndex ];
            childBounds.bottom = bottom[ childIndex ];
            childBounds.right  = right [ childIndex ];
        }
    }

    int getBottomOfPreviousChild( int childIndex ) {
        int result;

        if( childIndex != 0 )
            result = bottom[ childIndex - 1 ] - ( offset[ childIndex ] - offset[ childIndex - 1 ] );
        else if( parent != null )
            result = parent.getBottomOfPreviousChild( indexInParent );
        else
            result = Integer.MAX_VALUE;
        
        return result;
    }

    void repaint( DVIRectangle repaintBounds, int childIndex, long delay ) {
        if( parent != null ) {
            repaintBounds.offsetY( offset[ childIndex ] );
            parent.repaint( repaintBounds, indexInParent, delay );
        }
    }




    //  private

    private void addHasComponents( int index ) {
        if( hasComponents == null )
            hasComponents = new int[ kHasComponentsInitialLength ];
        else if( hasComponentsCount == hasComponents.length )
            setHasComponentsLength( hasComponents.length * 2 );
        
        hasComponents[ hasComponentsCount ++ ] = index;
    }

    private void addGetsMouseEvents( int index ) {
        if( getsMouseEvents == null )
            getsMouseEvents = new int[ kGetsMouseEventsInitialLength ];
        else if( getsMouseEventsCount == getsMouseEvents.length )
            setGetsMouseEventsLength( getsMouseEvents.length * 2 );
        
        getsMouseEvents[ getsMouseEventsCount ++ ] = index;
    }

    private void setChildLength( int length ) {
        View   [ ] oldChild  = child;
        boolean[ ] oldUnset  = unset;
        int    [ ] oldTop    = top;
        int    [ ] oldLeft   = left;
        int    [ ] oldBottom = bottom;
        int    [ ] oldRight  = right;
        int    [ ] oldOffset = offset;

        child  = new View   [ length ];
        unset  = new boolean[ length ];
        top    = new int    [ length ];
        left   = new int    [ length ];
        bottom = new int    [ length ];
        right  = new int    [ length ];
        offset = new int    [ length ];

        int copyLength = Math.min( oldChild.length, length );

        System.arraycopy( oldChild , 0, child , 0, copyLength );
        System.arraycopy( oldUnset , 0, unset , 0, copyLength );
        System.arraycopy( oldTop   , 0, top   , 0, copyLength );
        System.arraycopy( oldLeft  , 0, left  , 0, copyLength );
        System.arraycopy( oldBottom, 0, bottom, 0, copyLength );
        System.arraycopy( oldRight , 0, right , 0, copyLength );
        System.arraycopy( oldOffset, 0, offset, 0, copyLength );
    }

    private void setHasComponentsLength( int length ) {
        int[ ] oldHasComponents = hasComponents;
        hasComponents = new int[ length ];
        System.arraycopy( oldHasComponents, 0, hasComponents, 0, Math.min( oldHasComponents.length, length ));
    }

    private void setGetsMouseEventsLength( int length ) {
        int[ ] oldGetsMouseEvents = getsMouseEvents;
        getsMouseEvents = new int[ length ];
        System.arraycopy( oldGetsMouseEvents, 0, getsMouseEvents, 0, Math.min( oldGetsMouseEvents.length, length ));
    }




    public String toString( ) {
        return super.toString( ) + ", " +
            "totalBounds = " + totalBounds + ", " +
            "totalExpansion = " + totalExpansion;
    }

    void printStructure( String indent ) {
        String subIndent = indent + "  ";
        String subSubIndent = indent + "    ";

        super.printStructure( indent );
        System.out.println( subIndent + "hasComponentsCount = " + hasComponentsCount );
        System.out.println( subIndent + "getsMouseEventsCount = " + getsMouseEventsCount );
        System.out.println( subIndent + "childCount = " + childCount );

        for( int i = 0; i < childCount; ++ i ) {
            System.out.println( subIndent + "child " + i + ", " +
                "bounds " +
                    ( unset[ i ] ?
                        "unset" :
                        "= " +
                            top[ i ] + ", " +
                            left[ i ] + ", " +
                            bottom[ i ] + ", " +
                            right[ i ]
                    ) + ", " +
                "offset = " + offset[ i ] );

            child[ i ].printStructure( subSubIndent );
        }
    }
}
